<template>
  <div class="containers">
    <div class="my-process-designer__header">
      <a-button-group key="file-control">
        <a-button :type="headerButtonType" @click="uploadClick"
          >打开文件</a-button
        >
        <a-dropdown>
          <a-button :type="headerButtonType">下载文件</a-button>
          <a-menu slot="overlay">
            <a-menu-item key="1" @click="downloadProcessAsXml">
              下载为XML文件
            </a-menu-item>
            <a-menu-item key="2" @click="downloadProcessAsSvg">
              下载为SVG文件</a-menu-item
            >
            <a-menu-item key="3" @click="downloadProcessAsBpmn">
              下载为BPMN文件</a-menu-item
            >
          </a-menu>
        </a-dropdown>
        <a-button :type="headerButtonType" @click="saveClick">保存</a-button>
      </a-button-group>
      <a-button-group key="scale-control">
        <a-tooltip title="缩小视图">
          <a-button
            :disabled="zoom < 0.2"
            icon="zoom-out"
            @click="handleZoomIn"
          />
        </a-tooltip>
        <a-button>{{ Math.floor(this.zoom * 10 * 10) + "%" }}</a-button>
        <a-tooltip title="放大视图">
          <a-button
            :disabled="zoom > 4"
            icon="zoom-in"
            @click="handleZoomOut"
          />
        </a-tooltip>
        <a-tooltip title="重置视图并居中">
          <a-button icon="environment" @click="handleReZoom" />
        </a-tooltip>
      </a-button-group>
      <a-button-group key="stack-control1">
        <a-tooltip title="撤销">
          <a-button :disabled="!revocable" icon="undo" @click="handleUndo" />
        </a-tooltip>
        <a-tooltip title="恢复">
          <a-button :disabled="!recoverable" icon="redo" @click="handleRedo" />
        </a-tooltip>
        <a-tooltip title="重新绘制">
          <a-button icon="sync" @click="handleRestart" />
        </a-tooltip>
      </a-button-group>
      <a-button-group key="stack-control2">
        <a-dropdown>
          <a-button icon="eye"></a-button>
          <a-menu slot="overlay">
            <a-menu-item key="1" @click="handleProcessXML">
              预览XML
            </a-menu-item>
            <!-- <a-menu-item key="2" > 预览JSON</a-menu-item> -->
          </a-menu>
        </a-dropdown>
        <a-tooltip :title="simulationStatus ? '退出模拟' : '开启模拟'">
          <a-button icon="bug" @click="handleSimulation" />
        </a-tooltip>
      </a-button-group>
    </div>
    <div class="canvas" ref="canvas"></div>
    <BpmnPanelAntdv ref="BpmnPanelAntdv" :options="options" />
    <!-- 用于打开本地文件-->
    <input
      type="file"
      id="files"
      ref="refFile"
      style="display: none"
      accept=".xml, .bpmn"
      @change="importLocalFile"
    />
    <a-modal
      class="bpmn-design-modal"
      title="预览"
      width="60%"
      :footer="null"
      :visible.sync="previewModelVisible"
      @cancel="() => (this.previewModelVisible = false)"
      destroyOnClose
      :bodyStyle="{ height: '500px' }"
    >
      <codemirror v-model="bpmnStr" :options="cmOptions" />
    </a-modal>
  </div>
</template>

<script>
import BpmnModeler from "bpmn-js/lib/Modeler";
import CamundaBpmnModdle from "camunda-bpmn-moddle/resources/camunda.json";

import "bpmn-js/dist/assets/diagram-js.css";
import "bpmn-js/dist/assets/bpmn-js.css";
import "bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css";
// import "bpmn-js-properties-panel/dist/assets/properties-panel.css";

// 模拟流转流程
import TokenSimulationModule from "bpmn-js-token-simulation";
import "bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css";

import { BpmnPanelAntdv } from "@packages/index";
import { codemirror } from 'vue-codemirror';

// require styles
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/abcdef.css'

// language
import 'codemirror/mode/xml/xml' // xml编辑器模式

import {
  Menu,
  Dropdown,
  Tooltip
} from "ant-design-vue";
export default {
  name: "bpmn",
  components:{
    BpmnPanelAntdv,
    codemirror,
    AMenu:Menu,
    AMenuItem:Menu.Item,
    ADropdown:Dropdown,
    ATooltip:Tooltip
  },
  data() {  
    return {
      // bpmn建模器
      bpmnModeler: null,
      zoom: 1,
      shape: "",
      simulationStatus: false,
      recoverable: false,
      revocable: false,
      headerButtonSize: "",
      headerButtonType: "primary",
      cmOptions: {
        tabSize: 4,
        mode: "text/x-sql",
        theme: "abcdef",
        styleActiveLine: true,
        lineNumbers: true,
        line: true,
        // 加提示
        hintOptions: {
          completeSingle: false
        },
        height: 500
      },
      bpmnStr: "",
      previewModelVisible: false,
      options:{
        idAndNameDisabled:true,//流程id和name禁用编辑
        async assigneeApi(parms){
          /**
           * 不分页示例
           * parms为查询及分页参数，需要自行处理
           * 返回参数需要按该格式组装
           */
          return [
            {label:'管理员',value:'admin'},
            {label:'xys',value:'xys'}
            ]
        },
        candidateUsersApi(parms){
          /**
           * 分页示例
           * parms为查询及分页参数，需要自行处理
           * 返回参数需要按该格式组装
           */
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve({
                total:100,
                result:[
                  {label:'管理员',value:'admin'},
                  {label:'张三',value:'zhangsan'}
                ]
              }) 
            },2000)
          })
        },
        candidateGroupsApi(){
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve({
                total:100,
                result:[
                  {label:'管理员',value:'admin'},
                  {label:'普通用户',value:'user'}
                ]
              }) 
            },2000)
          })
        },
        formKeyApi(){
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve({
                total:100,
                result:[
                  {label:'请假单',value:'test1'},
                  {label:'出差申请单',value:'test2'}
                ]
              }) 
            },2000)
          })
        }
      }
    };
  },
  mounted() {
    this.$nextTick(() => {
      this.initBpmn();
    });
  },
  methods: {
    initBpmn() {
      const canvas = this.$refs.canvas;
      this.bpmnModeler = new BpmnModeler({// 建模
        container: canvas,
        propertiesPanel: {
          parent: "#js-properties-panel" // 添加控制板
        },
        additionalModules: [
          TokenSimulationModule
        ],
        moddleExtensions: {
          camunda: CamundaBpmnModdle
        }
      });
      this.$refs.BpmnPanelAntdv.init(this.bpmnModeler);
      this.createNewDiagram();
    },
    async createNewDiagram(xml) {
      try {
        if (!xml) {
          await this.bpmnModeler.createDiagram();
          this.shape = this.bpmnModeler.get("canvas")._rootElement;
          const modeling = this.bpmnModeler.get("modeling");
          if (this.shape) {
            modeling.updateProperties(this.shape, {
              isExecutable: true
            });
          }
        } else {
          await this.bpmnModeler.importXML(xml);
        }
        // 回写模板名称及添加事件
        this.success();
        // 图形自适应
        var canvas = this.bpmnModeler.get("canvas");
        canvas.zoom("fit-viewport");
      } catch (error) {
        console.warn(error);
      }
    },
    success() {
      this.addBpmnListener();
    },
    // 添加绑定事件
    addBpmnListener() {
      try {
        this.recoverable = this.bpmnModeler.get("commandStack").canRedo();
        this.revocable = this.bpmnModeler.get("commandStack").canUndo();
      } catch (e) {
        console.error(`[Process Designer Warn]: ${e.message || e}`);
      }
    },
    handleUndo() {
      // 后退
      this.bpmnModeler.get("commandStack").undo();
    },
    handleRedo() {
      // 前进
      this.bpmnModeler.get("commandStack").redo();
    },
    handleRestart() {
      this.recoverable = false;
      this.revocable = false;
      this.createNewDiagram(null).then(() =>
        this.bpmnModeler.get("canvas").zoom(1, "auto")
      );
    },
    handleZoomIn() {
      this.zoom += 0.1;
      var canvas = this.bpmnModeler.get("canvas");
      canvas.zoom(this.zoom);
    },
    handleZoomOut() {
      this.zoom -= 0.1;
      var canvas = this.bpmnModeler.get("canvas");
      canvas.zoom(this.zoom);
    },
    handleReZoom() {
      this.zoom = 1;
      this.bpmnModeler.get("canvas").zoom("fit-viewport", "auto");
    },
    saveClick() {
      this.getDiagramXml().then(data => {
        this.$emit("ok", data);
      });
    },
    async getDiagramXml(done) {
      try {
        const result = await this.bpmnModeler.saveXML({ format: true });
        const result2 = await this.bpmnModeler.saveSVG();
        const { xml } = result;
        const { svg } = result2;
        return {
          xml,
          svg
        };
      } catch (err) {
        console.log(err);
      }
    },
    handleProcessXML() {
      this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
        this.bpmnStr = xml;
        this.cmOptions.mode = "application/xml";
        this.previewModelVisible = true;
      });
    },
    handleSimulation() {
      this.simulationStatus = !this.simulationStatus;
      this.bpmnModeler.get("toggleMode").toggleMode();
    },
    uploadClick() {
      this.$refs.refFile.click();
    },
    // 加载本地文件
    importLocalFile() {
      const that = this;
      const file = this.$refs.refFile.files[0];
      const reader = new FileReader();
      reader.readAsText(file);
      reader.onload = function() {
        const xmlStr = this.result;
        that.createNewDiagram(xmlStr);
      };
    },
    downloadProcessAsXml() {
      this.downloadProcess("xml");
    },
    downloadProcessAsBpmn() {
      this.downloadProcess("bpmn");
    },
    downloadProcessAsSvg() {
      this.downloadProcess("svg");
    },
    // 下载流程图到本地
    async downloadProcess(type, name) {
      try {
        const _this = this;
        // 按需要类型创建文件并下载
        if (type === "xml" || type === "bpmn") {
          const { err, xml } = await this.bpmnModeler.saveXML();
          // 读取异常时抛出异常
          if (err) {
            console.error(`[Process Designer Warn ]: ${err.message || err}`);
          }
          const { href, filename } = _this.setEncoded(
            type.toUpperCase(),
            name,
            xml
          );
          downloadFunc(href, filename);
        } else {
          const { err, svg } = await this.bpmnModeler.saveSVG();
          // 读取异常时抛出异常
          if (err) {
            return console.error(err);
          }
          const { href, filename } = _this.setEncoded("SVG", name, svg);
          downloadFunc(href, filename);
        }
      } catch (e) {
        console.error(`[Process Designer Warn ]: ${e.message || e}`);
      }
      // 文件下载方法
      function downloadFunc(href, filename) {
        if (href && filename) {
          const a = document.createElement("a");
          a.download = filename; // 指定下载的文件名
          a.href = href; //  URL对象
          a.click(); // 模拟点击
          URL.revokeObjectURL(a.href); // 释放URL 对象
        }
      }
    },
    // 当图发生改变的时候会调用这个函数，这个data就是图的xml
    setEncoded(type, filename = "diagram", data) {
      const encodedData = encodeURIComponent(data);
      return {
        filename: `${filename}.${type}`,
        href: `data:application/${
          type === "svg" ? "text/xml" : "bpmn20-xml"
        };charset=UTF-8,${encodedData}`,
        data: data
      };
    }
  }
};
</script>
<style lang="less">
.containers .djs-palette.two-column.open {
  width: 100px !important;
}
.bts-toggle-mode {
  display: none !important;
}
.my-process-designer__header {
  width: 100%;
  min-height: 36px;
  display: flex;
  .ant-btn-group {
    margin: 4px;
  }
  .align {
    position: relative;
    i {
      &:after {
        content: "|";
        position: absolute;
        transform: rotate(90deg) translate(200%, 60%);
      }
    }
  }
  .align.align-left i {
    transform: rotate(90deg);
  }
  .align.align-right i {
    transform: rotate(-90deg);
  }
  .align.align-top i {
    transform: rotate(180deg);
  }
  .align.align-bottom i {
    transform: rotate(0deg);
  }
  .align.align-center i {
    transform: rotate(90deg);
    &:after {
      transform: rotate(90deg) translate(0, 60%);
    }
  }
  .align.align-middle i {
    transform: rotate(0deg);
    &:after {
      transform: rotate(90deg) translate(0, 60%);
    }
  }
}
.bpmn-design-modal {
  .vue-codemirror,
  .CodeMirror {
    height: 100%;
  }
}
</style>
<style scoped>
.canvas {
  position: absolute;
  left: 0;
  top: 50px;
  right: 0;
  bottom: 0;
}
.containers {
  width: 100%;
  height: 100vh;
  background-color: #ffffff;
  background: white;
  overflow: auto;
  background-image: linear-gradient(
      90deg,
      rgba(220, 220, 220, 0.5) 6%,
      transparent 0
    ),
    linear-gradient(rgba(192, 192, 192, 0.5) 6%, transparent 0);
  background-size: 12px 12px;
  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
}
</style>
